using System;
using System.Collections.Generic;
using System.Text;

namespace Decav.Financial
{
    /// <summary>
    /// <para>
    /// The Committee on Uniform Security Identification Procedures  9-digit alphanumeric
    /// security identifier that they distribute for all North American securities for the 
    /// purposes of facilitating clearing and settlement of trades.
    /// </para>
    /// <remarks>
    /// The CUSIP distribution 
    /// system is owned by the American Bankers Association and is operated by Standard & Poor's.
    /// The CUSIP Services Bureau acts as the National Numbering Association (NNA) for North America,
    /// and the CUSIP serves as the National Securities Identification Number for products issued
    /// from both the United States and Canada.
    /// </remarks>
    /// </summary>
    [Serializable]
    public struct Cusip
    {
        /// <summary>
        /// Initializes a new <see cref="Cusip"/>.
        /// </summary>
        /// <param name="value">The 9 character string that is the CUSIP.</param>
        public Cusip(string value)
        {
            if (value == null)
                throw new ArgumentNullException("value");

            if (value.Length != 9)
                throw new ArgumentException("A CUSIP must be 9 characters in length.");

            // All CUSIPS will be uppercase to avoid comparison errors.
            _idInternal = value.ToUpper();
        }

        /// <summary>
        /// Holds the internal value of the ID.
        /// </summary>
        private string _idInternal;

        /// <summary>
        /// The empty value of a <see cref="Cusip"/>.
        /// </summary>
        public static readonly Cusip Empty = new Cusip();

        private static readonly string _alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

        /// <summary>
        /// Gets a string representation of the Cusip.
        /// </summary>
        /// <returns>The ID as a string.</returns>
        public override string ToString()
        {
            if (_idInternal == null)
                return "---------";

            return _idInternal;
        }

        /// <summary>
        /// Gets if the value of the CUSIP is a value that is reserved, or not used by the 
        /// official CUSIP board.
        /// </summary>
        public bool ReservedValue
        {
            get
            {
                if (_idInternal == null)
                    return true;

                int idx0 = _alphabet.IndexOf(_idInternal[0]);
                int idx1 = _alphabet.IndexOf(_idInternal[1]);
                int idx3 = _alphabet.IndexOf(_idInternal[3]);
                int idx4 = _alphabet.IndexOf(_idInternal[4]);

                return ((idx0 == 9 && idx1 >= 9) || idx0 > 9) ||
                    ((idx3 == 9 && idx4 >= 9) || idx3 > 9);
            }
        }

        /// <summary>
        /// Gets the code of the company issuing the CUSIP.
        /// </summary>
        public string IssuerCode
        {
            get
            {
                if (_idInternal == null)
                    return string.Empty;

                return _idInternal.Substring(0, 6);
            }
        }

        /// <summary>
        /// Gets the code of the specific issue of CUSIP.
        /// </summary>
        public string IssueCode
        {
            get
            {
                if (_idInternal == null)
                    return string.Empty;

                return _idInternal.Substring(6, 2);
            }
        }

        /// <summary>
        /// Gets the check digit (9th digit of the CUSIP), that checks the rest of the value
        /// for validity.
        /// </summary>
        public string CheckDigit
        {
            get
            {
                if (_idInternal == null)
                    return string.Empty;    

                return _idInternal.Substring(8);
            }
        }

        /// <summary>
        /// Gets if the CUSIP is a valid CUSIP.
        /// </summary>
        public bool IsValid
        {
            get
            {
                if (_idInternal == null)
                    return false;

                return GetCheckDigit(_idInternal.Substring(0, 8)).ToString() == CheckDigit;
            }
        }

        /// <summary>
        /// Gets the check digit for an 8 digit CUSIP.
        /// </summary>
        /// <param name="cusip8">The 8 digits of a CUSIP that the check digit should be
        /// created for.</param>
        /// <returns>The check digit.</returns>
        public static char GetCheckDigit(string cusip8)
        {
            if (cusip8 == null)
                throw new ArgumentNullException("cusip8");

            if (cusip8.Length != 8)
                throw new ArgumentException("The CUSIP length to get a check digit must be 8 characters.");

            int evens = 0;
            int odds = 0;
            for (int i = 0; i < 8; i++)
            {
                int value = _alphabet.IndexOf(cusip8[i]);

                if (i % 2 == 0) // even
                {
                    int tens = 0;
                    if (value % 10 > 0)
                    {
                        tens = (int)Math.Floor((decimal)value / 10);
                    }

                    odds += tens + (value - (tens * 10));
                }
                else // odd
                {
                    value = value * 2;
                    int tens = 0;
                    if (value % 10 > 0)
                    {
                        tens = (int)Math.Floor((decimal)value / 10);
                    }

                    odds += tens + (value - (tens * 10));
                }
            }

            return _alphabet[(odds+evens)%10];
        }

        /// <summary>
        /// Checks the validity of a CUSIP String, including length and check digit.
        /// CUSIPS in the reserved block do not require a valid check digit.
        /// </summary>
        /// <param name="cusip">The CUSIP to verify.</param>
        /// <returns>True when the CUSIP is valid, else false.</returns>
        public static bool CheckIsValid(string cusip)
        {
            if (cusip == null)
                return false;

            if (cusip.Length != 9)
                return false;

            Cusip c = new Cusip(cusip);

            return (c.IsValid || c.ReservedValue);
        }

        /// <summary>
        /// Gets the unique hashcode of the <see cref="Cusip"/>.
        /// </summary>
        /// <returns>The hashcode of the id.</returns>
        public override int GetHashCode()
        {
            return ToString().GetHashCode();
        }

        /// <summary>
        /// Checks if two <see cref="Cusip"/> are equal.
        /// </summary>
        /// <param name="obj">The <see cref="Cusip"/> to check equality with.</param>
        /// <returns>True if the two are equal, false if they are inequal.</returns>
        public override bool Equals(object obj)
        {
            if (obj.GetType() != typeof(Cusip))
                return false;

            Cusip ident = (Cusip)obj;

            // Check that the two are equal.
            return this._idInternal == ident._idInternal;
        }

        /// <summary>
        /// Evaluates the equality of two <see cref="Cusip"/>.
        /// </summary>
        /// <param name="left">The left argument.</param>
        /// <param name="right">The right argument.</param>
        /// <returns>True if the two are equal, false if they are inequal.</returns>
        public static bool operator ==(Cusip left, Cusip right)
        {
            return left.Equals(right);
        }

        /// <summary>
        /// Evaluates the equality of two <see cref="Cusip"/>.
        /// </summary>
        /// <param name="left">The left argument.</param>
        /// <param name="right">The right argument.</param>
        /// <returns>True if the two are inequal, false if they are equal.</returns>
        public static bool operator !=(Cusip left, Cusip right)
        {
            return !left.Equals(right);
        }

        /// <summary>
        /// Casts an integer to a <see cref="AgreementIdentifier"/>.
        /// </summary>
        /// <param name="i">The string to cast.</param>
        /// <returns>A new <see cref="AgreementIdentifier"/> for the integer.</returns>
        public static explicit operator Cusip(string i)
        {
            try
            {
                return new Cusip(i);
            }
            catch (ArgumentException ex)
            {
                throw new InvalidCastException("The value cannot be cast to a Cusip because the string is not a valid Cusip.", ex);
            }
        }
    }
}
